﻿#region

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using PTCN.CrossPlatform.BettingGame.CardGameFramework.Config;
using PTCN.CrossPlatform.BettingGame.CardGameFramework.Exceptions;
using PTCN.CrossPlatform.BettingGame.DataAccess.Types;
using PTCN.CrossPlatform.BettingGame.Utilities;
using PTCN.CrossPlatform.BettingGame.Bingo.Factories;
using PTCN.CrossPlatform.BettingGame.Bingo.Models;

#endregion

namespace PTCN.CrossPlatform.BettingGame.Bingo.Controllers
{
    public class SessionHandler
    {
        // Singleton instance
        private static readonly Lazy<SessionHandler> _instance = new Lazy<SessionHandler>(() => new SessionHandler());
        private readonly ConcurrentDictionary<long, SedieRoom> _sessions;
        private readonly ConcurrentQueue<SedieRoom> _garbageQueue;
        private readonly SedieGameFactory _factory;
        private readonly int _maxHangout = 900;

        public static SessionHandler Instance
        {
            get { return _instance.Value; }
        }

        private SessionHandler()
        {
            _sessions = new ConcurrentDictionary<long, SedieRoom>();
            _factory = new SedieGameFactory();
            _garbageQueue = new ConcurrentQueue<SedieRoom>();
        }

        public SediePlayer RegisterPlayer(long accountId, string username, SedieRoom session, string connectionId)
        {
            if (session == null)
            {
                return null;
            }
            SediePlayer player = PlayerHandler.Instance.GetPlayer(accountId);
            if (player == null)
            {
                return null;
            }
            if (session.IsFull)
            {
                throw new InvalidActionException("Bàn chơi đã đầy.");
            }
            if (!session.CanBePlayer(player.Account))
            {
                throw new NotEnoughMoneyException("Bạn không đủ tiền vào bàn.");
            }

            //kiem tra player.SessionID co ton tai va khac voi session.SessionId cua ban choi nay khong?
            // neu khac thi remove player khoi session cu
            if (player.SessionID > 0 && player.SessionID != session.SessionId)
            {
                throw new InvalidActionException("Bạn không vào được bàn này vì bạn đang chơi ở bàn chơi khác.");
            }

            if (session.AddPlayer(player))
            {
                //Đã setSessionID(), ChangeConnectionStatus() trong hàm session.AddPlayer();
                //player.setSessionID(session.SessionId);
                ////Set connection cho player
                //player.ChangeConnectionStatus(ConnectionStatus.CONNECTED);
                if (session.EnableStartGame())
                {
                    session.NotifyStartGame();
                }
                return player;
            }
            return null;
        }

        /// <summary>
        /// Remove khoi danh sach nguoi choi.
        /// </summary>
        /// <param name="accountId"></param>
        /// <param name="sessionId"></param>
        /// <returns></returns>
        public bool UnregisterPlayer(long accountId, BettingGameSession session)
        {
            //PlayerHandler.Instance.RemovePlayer(accountId);
            if (session == null) return false;
            session.RemovePlayer(accountId);
            return true;
        }

        #region find_create_get_session

        /// <summary>
        /// Tim session cho nguoi choi.
        /// </summary>
        /// <param name="gameId"></param>
        /// <param name="minBet"></param>
        /// <param name="moneyType"></param>
        /// <returns></returns>
        public SedieRoom FindSession(int minBet, byte moneyType, string ip)
        {
            if (minBet < 1 || moneyType > 1)
            {
                return null;
            }
            try
            {
                List<SedieRoom> validSessions = new List<SedieRoom>();
                foreach (var keyValue in _sessions)
                {
                    long sessionid = keyValue.Key;
                    SedieRoom session = keyValue.Value;
                    bool condition = !session.IsFull && !session.IsPrivate &&
                                     session.Active && session.MinBet == minBet &&
                                     session.MoneyType == moneyType && session.IsAllow &&
                                     !session.DuplicateIp(ip);
                    if (condition)
                    {
                        validSessions.Add(session);
                    }
                }

                int validCount = validSessions.Count;
                if (validCount > 0)
                {
                    Random rand = new Random();
                    List<SedieRoom> lstReduce = new List<SedieRoom>(validSessions.Where(s => !s.IsEnough));
                    int lstCount = lstReduce.Count;
                    if (lstCount > 0)
                    {
                        return lstReduce[rand.Next(lstCount)];
                    }

                    lstReduce = new List<SedieRoom>(validSessions.Where(s => !s.IsPlaying && !s.IsComfirming));
                    lstCount = lstReduce.Count;
                    return lstCount > 0 ? lstReduce[rand.Next(lstCount)] : validSessions[rand.Next(validCount)];
                }

                //Tao ra 1 public session va add vao collections.
                SedieRoom retSession = CreateSession(minBet, moneyType, (byte) SedieRules.Normal, string.Empty, false);
                return retSession;
            }
            catch (Exception ex)
            {
                NLogManager.PublishException(ex);
            }
            return null;
        }

        /// <summary>
        /// Tao 1 session moi.
        /// </summary>
        /// <param name="gameId"></param>
        /// <param name="minBet"></param>
        /// <param name="moneyType"></param>
        /// <returns></returns>
        public SedieRoom CreateSession(int minBet, byte moneyType, byte rule, string password, bool solo)
        {
            SedieRoom _session = _factory.CreateSession(minBet, moneyType, rule, password, solo);
            if (_session == null) return null;
            SedieGameLoop _gameLoop = _factory.CreateGameLoop(_session);
            if (_gameLoop != null)
            {
                _session.SetGameLoop(_gameLoop);
                if (_sessions.TryAdd(_session.SessionId, _session))
                {
                    return _session;
                }
            }

            //Neu co loi xay ra khi tao gameloop thi remove ten phong.
            _factory.RemoveName(_session.Name.ToString());
            return null;
        }

        /// <summary>
        /// Get session by sessionId
        /// </summary>
        /// <param name="sessionId"></param>
        /// <returns></returns>
        public SedieRoom GetSession(long sessionId)
        {
            if (sessionId < 1)
            {
                return null;
            }
            try
            {
                SedieRoom session = null;
                if (_sessions.TryGetValue(sessionId, out session))
                {
                    return session.Active ? session : null;
                }
            }
            catch (Exception ex)
            {
                NLogManager.PublishException(ex);
            }
            return null;
        }

        /// <summary>
        /// Get session by name and password
        /// </summary>
        /// <param name="sessionId"></param>
        /// <returns></returns>
        public SedieRoom GetSession(string name, string password)
        {
            if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(password))
            {
                return null;
            }
            try
            {
                SedieRoom session =
                    _sessions.Values.FirstOrDefault(s => s.Name.Equals(name) && s.Password.Equals(password));
                if (session == null) return null;
                return !session.Active ? null : session;
            }
            catch (Exception ex)
            {
                NLogManager.PublishException(ex);
            }
            return null;
        }

        /// <summary>
        /// Get session by accountid.
        /// </summary>
        /// <param name="accountId"></param>
        /// <returns></returns>
        public SedieRoom GetSessionByAccount(long accountId)
        {
            if (accountId < 1)
            {
                return null;
            }
            try
            {
                SediePlayer player = PlayerHandler.Instance.GetPlayer(accountId);
                if (player != null)
                {
                    if (player.SessionID > 0)
                    {
                        return GetSession(player.SessionID);
                    }
                }
            }
            catch (Exception ex)
            {
                NLogManager.PublishException(ex);
            }
            return null;
        }

        #endregion

        #region start_destroy_session

        public void DeactiveHangoutSession()
        {
            foreach (var session in _sessions.Values)
            {
                if (session.IsEnough)
                {
                    if (session.IdleTime() >= _maxHangout)
                    {
                        DeactiveSession(session);
                    }
                }
                else
                {
                    if (session.IdleTime() >= _maxHangout*3)
                    {
                        DeactiveSession(session);
                    }
                }
            }
        }

        public void DeactiveSession(SedieRoom session)
        {
            if (session == null)
            {
                return;
            }
            _factory.RemoveName(session.Name);
            SedieRoom sess = null;
            if (!_sessions.TryRemove(session.SessionId, out sess)) return;
            _garbageQueue.Enqueue(sess);
            foreach (var key in session.Players.Values)
            {
                key.ResetBeforeQuitRoom();
                ConnectionHandler.Instance.Clients.Group(session.SessionId.ToString()).playerLeave(key.AccountID);
            }
            NLogManager.LogMessage(string.Format("DeactiveSession: {0}", sess.SessionId));
        }

        public void RemoveInactiveSessions()
        {
            try
            {
                while (!_garbageQueue.IsEmpty)
                {
                    SedieRoom session = null;
                    if (!_garbageQueue.TryDequeue(out session)) continue;
                    NLogManager.LogMessage(string.Format("RemoveInactiveSessions: {0}", session.SessionId));
                    session.Dispose();
                }
            }
            catch (Exception ex)
            {
                NLogManager.PublishException(ex);
            }
        }

        /// <summary>
        /// Chi dung khi muon huy 1 session.
        /// </summary>
        /// <param name="sessionId"></param>
        /// <returns></returns>
        public bool RemoveSession(long sessionId)
        {
            if (!_sessions.ContainsKey(sessionId)) return false;
            SedieRoom session = null;
            if (!_sessions.TryRemove(sessionId, out session)) return false;
            _factory.RemoveName(session.Name);
            foreach (var key in session.Players.Keys)
            {
                session.RemovePlayer(key);
            }
            return true;
        }

        public void CountPlayer(out int vip, out int normal)
        {
            vip = 0;
            normal = 0;
            if (_sessions.IsEmpty)
            {
                return;
            }
            foreach (var session in _sessions.Values)
            {
                switch (session.MoneyType)
                {
                    case (byte) GameMoneyType.COIN:
                        normal += session.CountPlayers;
                        break;
                    case (byte) GameMoneyType.TOTALSTAR:
                        vip += session.CountPlayers;
                        break;
                }
            }
        }

        #endregion
    }
}